package cli

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/fatih/color"
	"github.com/wk8/go-ordered-map/v2"
	"golang.org/x/term"

	"github.com/docker/cagent/pkg/input"
	"github.com/docker/cagent/pkg/tools"
)

// ConfirmationResult represents the result of a user confirmation prompt
type ConfirmationResult string

const (
	ConfirmationApprove        ConfirmationResult = "approve"
	ConfirmationApproveSession ConfirmationResult = "approve_session"
	ConfirmationReject         ConfirmationResult = "reject"
	ConfirmationAbort          ConfirmationResult = "abort"
)

var bold = color.New(color.Bold).SprintfFunc()

type Printer struct {
	out io.Writer
}

func NewPrinter(out io.Writer) *Printer {
	return &Printer{
		out: out,
	}
}

func (p *Printer) Println(a ...any) {
	fmt.Fprintln(p.out, a...)
}

func (p *Printer) Print(a ...any) {
	fmt.Fprint(p.out, a...)
}

func (p *Printer) Printf(format string, a ...any) (n int, err error) {
	return fmt.Fprintf(p.out, format, a...)
}

// PrintWelcomeMessage prints the welcome message
func (p *Printer) PrintWelcomeMessage(appName string) {
	p.Printf("\n------- Welcome to %s! -------\n(Ctrl+C to stop the agent and exit)\n\n", bold(appName))
}

// PrintError prints an error message
func (p *Printer) PrintError(err error) {
	p.Printf("❌ %s", err)
}

// PrintAgentName prints the agent name header
func (p *Printer) PrintAgentName(agentName string) {
	p.Printf("\n--- Agent: %s ---\n", bold(agentName))
}

// PrintToolCall prints a tool call
func (p *Printer) PrintToolCall(toolCall tools.ToolCall) {
	p.Printf("\nCalling %s%s\n", bold(toolCall.Function.Name), formatToolCallArguments(toolCall.Function.Arguments))
}

// PrintToolCallWithConfirmation prints a tool call and prompts for confirmation
func (p *Printer) PrintToolCallWithConfirmation(ctx context.Context, toolCall tools.ToolCall, rd io.Reader) ConfirmationResult {
	p.Printf("\n%s\n", bold("🛠️ Tool call requires confirmation 🛠️"))
	p.PrintToolCall(toolCall)
	p.Printf("\n%s", bold("Can I run this tool? ([y]es/[a]ll/[n]o): "))

	// Try single-character input from stdin in raw mode (no Enter required)
	fd := int(os.Stdin.Fd())
	if oldState, err := term.MakeRaw(fd); err == nil {
		defer func() {
			if err := term.Restore(fd, oldState); err != nil {
				p.Printf("\nFailed to restore terminal state: %v\n", err)
			}
		}()
		buf := make([]byte, 1)
		for {
			if _, err := os.Stdin.Read(buf); err != nil {
				break
			}
			switch buf[0] {
			case 'y', 'Y':
				p.Print(bold("Yes 👍"))
				return ConfirmationApprove
			case 'a', 'A':
				p.Print(bold("Yes to all 👍"))
				return ConfirmationApproveSession
			case 'n', 'N':
				p.Print(bold("No 👎"))
				return ConfirmationReject
			case 3: // Ctrl+C
				return ConfirmationAbort
			case '\r', '\n':
				// ignore
			default:
				// ignore other keys
			}
		}
	}

	// Fallback: line-based scanner (requires Enter)
	text, err := input.ReadLine(ctx, rd)
	if err != nil {
		return ConfirmationReject
	}

	switch text {
	case "y":
		return ConfirmationApprove
	case "a":
		return ConfirmationApproveSession
	case "n":
		return ConfirmationReject
	default:
		// Default to reject for invalid input
		return ConfirmationReject
	}
}

// PrintToolCallResponse prints a tool call response
func (p *Printer) PrintToolCallResponse(toolCall tools.ToolCall, response string) {
	p.Printf("\n%s response%s\n", bold(toolCall.Function.Name), formatToolCallResponse(response))
}

// PromptMaxIterationsContinue prompts the user to continue after max iterations
func (p *Printer) PromptMaxIterationsContinue(ctx context.Context, maxIterations int) ConfirmationResult {
	p.Printf("\n⚠️  Maximum iterations (%d) reached. The agent may be stuck in a loop.\n", maxIterations)
	p.Println("This can happen with smaller or less capable models.")
	p.Println("\nDo you want to continue for 10 more iterations? (y/n):")

	response, err := input.ReadLine(ctx, os.Stdin)
	if err != nil {
		p.Println("\nFailed to read input, exiting...")
		return ConfirmationAbort
	}

	response = strings.TrimSpace(strings.ToLower(response))
	if response == "y" || response == "yes" {
		p.Print("✓ Continuing...\n\n")
		return ConfirmationApprove
	} else {
		p.Print("Exiting...\n\n")
		return ConfirmationReject
	}
}

// PromptOAuthAuthorization prompts the user for OAuth authorization
func (p *Printer) PromptOAuthAuthorization(ctx context.Context, serverURL string) ConfirmationResult {
	p.Println("\n🔐 OAuth Authorization Required")
	p.Println("Server:", serverURL, "(remote)")
	p.Println("This server requires OAuth authentication to access its tools.")
	p.Println("Your browser will open automatically to complete the authorization.")
	p.Printf("\n%s (y/n): ", "Do you want to authorize access?")

	response, err := input.ReadLine(ctx, os.Stdin)
	if err != nil {
		p.Println("\nFailed to read input, aborting authorization...")
		return ConfirmationAbort
	}

	response = strings.TrimSpace(strings.ToLower(response))
	if response == "y" || response == "yes" {
		p.Println("✓ Starting OAuth authorization...")
		p.Println("Please complete the authorization in your browser.")
		p.Print("Once completed, the agent will continue automatically.\n\n")
		return ConfirmationApprove
	} else {
		p.Print("Authorization declined. Exiting...\n\n")
		return ConfirmationReject
	}
}

func formatToolCallArguments(arguments string) string {
	if arguments == "" {
		return "()"
	}

	// Is is a map?
	kv := orderedmap.New[string, any]()
	if err := json.Unmarshal([]byte(arguments), &kv); err == nil {
		if kv.Len() == 0 {
			return "()"
		}

		var (
			parts     []string
			multiline bool
		)

		for key, value := range kv.FromOldest() {
			formatted := formatJSONValue(key, value)
			parts = append(parts, formatted)

			multiline = multiline || strings.Contains(formatted, "\n")
		}

		if len(parts) == 1 && !multiline {
			return fmt.Sprintf("(%s)", parts[0])
		}

		return fmt.Sprintf("(\n  %s\n)", strings.Join(parts, "\n  "))
	}

	// Maybe some other JSON type?
	var parsed any
	if err := json.Unmarshal([]byte(arguments), &parsed); err == nil {
		formatted, _ := json.MarshalIndent(parsed, "", "  ")
		return fmt.Sprintf("(%s)", string(formatted))
	}

	// JSON parsing failed
	return fmt.Sprintf("(%s)", arguments)
}

func formatToolCallResponse(response string) string {
	if response == "" {
		return " → ()"
	}

	// For responses, we want to show them as readable text, not JSON
	// Check if it looks like JSON first
	var parsed any
	if err := json.Unmarshal([]byte(response), &parsed); err == nil {
		// It's valid JSON, format it nicely
		return " → " + formatParsedJSON(parsed)
	}

	// It's plain text, handle multiline content
	if strings.Contains(response, "\n") {
		// Trim whitespace and split into lines
		trimmed := strings.TrimSpace(response)
		lines := strings.Split(trimmed, "\n")

		if len(lines) <= 3 {
			// Short multiline, show inline
			return fmt.Sprintf(" → %q", response)
		}

		// Long multiline, format with line breaks
		// Process each line individually and collapse consecutive empty lines
		var formatted []string
		lastWasEmpty := false

		for _, line := range lines {
			trimmedLine := strings.TrimSpace(line)
			if trimmedLine == "" {
				// Empty line - only add one if the last line wasn't empty
				if !lastWasEmpty {
					formatted = append(formatted, "")
					lastWasEmpty = true
				}
			} else {
				formatted = append(formatted, line)
				lastWasEmpty = false
			}
		}
		return fmt.Sprintf(" → (\n%s\n)", strings.Join(formatted, "\n"))
	}

	// Single line text response
	return fmt.Sprintf(" → %q", response)
}

func formatParsedJSON(data any) string {
	switch v := data.(type) {
	case map[string]any:
		if len(v) == 0 {
			return "()"
		}

		parts := make([]string, 0, len(v))
		hasMultilineContent := false

		for key, value := range v {
			formatted := formatJSONValue(key, value)
			parts = append(parts, formatted)
			if strings.Contains(formatted, "\n") {
				hasMultilineContent = true
			}
		}

		if len(parts) == 1 && !hasMultilineContent {
			return fmt.Sprintf("(%s)", parts[0])
		}

		return fmt.Sprintf("(\n  %s\n)", strings.Join(parts, "\n  "))

	default:
		// For non-object types, use standard JSON formatting
		formatted, _ := json.MarshalIndent(data, "", "  ")
		return fmt.Sprintf("(%s)", string(formatted))
	}
}

func formatJSONValue(key string, value any) string {
	switch v := value.(type) {
	case string:
		// Handle multiline strings by displaying with actual newlines
		if strings.Contains(v, "\n") {
			// Format as: key: "content with
			// actual line breaks"
			return fmt.Sprintf("%s: %q", bold(key), v)
		}
		// Regular string with proper escaping
		return fmt.Sprintf("%s: %q", bold(key), v)

	case []any:
		if len(v) == 0 {
			return fmt.Sprintf("%s: []", bold(key))
		}
		// Show full array contents
		jsonBytes, _ := json.MarshalIndent(v, "", "  ")
		return fmt.Sprintf("%s: %s", bold(key), string(jsonBytes))

	case map[string]any:
		jsonBytes, _ := json.MarshalIndent(v, "", "  ")
		return fmt.Sprintf("%s: %s", bold(key), string(jsonBytes))

	default:
		jsonBytes, _ := json.Marshal(v)
		return fmt.Sprintf("%s: %s", bold(key), string(jsonBytes))
	}
}
